﻿using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace SyncUdp.Network
{
    using SyncUdp.Enum;
    using SyncUdp.Event.GameEvent;
    using SyncUdp.Event.NetworkEvent;
    using SyncUdp.Interface;
    using SyncUdp.Utility;
    using System;
    using System.Collections.Concurrent;
    using System.Net;
    using System.Net.Sockets;
    using System.Threading;
    using System.Threading.Tasks;

    /// <summary>
    /// 基于 UDP 的客户端
    /// </summary>
    public class GameUdpClient : AbstractGameNetwork
    {
        bool _isOnline;
        int _handledFrames;
        int _playerId;
        string _playerName;
        ConcurrentQueue<List<ClientInputsEvent>> _inputsCache;
        ConcurrentQueue<(int, bool)> _diconnectedPlayersCache;

        public int HandledFrames { get => _handledFrames; set => _handledFrames = value; }

        public int PlayerId { get => _playerId; }

        public string PlayerName { get => _playerName; }

        /// <summary>
        /// 远端主机的 IP（前提是已经创建默认主机）
        /// </summary>
        public IPEndPoint RemoteIP { get => (IPEndPoint)_udpClient.Client.RemoteEndPoint; }

        public GameUdpClient(IPEndPoint clientEP, IPEndPoint serverEP, 
            int playerId, string playerName, string logFileName = "Client") 
            : base(clientEP, logFileName)
        {
            _udpClient.Connect(serverEP);
            _playerId = playerId;
            _playerName = playerName;
            _handledFrames = 0;
            _isOnline = false;
            _inputsCache = new ConcurrentQueue<List<ClientInputsEvent>>();
            _diconnectedPlayersCache = new ConcurrentQueue<(int, bool)>();
        }

        public GameUdpClient(UdpClient udpClient, int playerId, string playerName, string logFileName = "Client")
            : base(udpClient, logFileName)
        {
            _playerId = playerId;
            _playerName = playerName;
            _handledFrames = 0;
            _isOnline = false;
            _inputsCache = new ConcurrentQueue<List<ClientInputsEvent>>();
            _diconnectedPlayersCache = new ConcurrentQueue<(int, bool)>();
        }

        public override void Start(IGameManager gm)
        {
            base.Start();
            SendIdAndNameOnJoiningGame();
            _logUtility.Logging($"1. GameUdpClient：开始发送心跳包");
            _isOnline = true;
            CheckDisconnection();
        }

        #region 客户端操作
        void SendMsg(NetworkEventType eventType, INetworkEventArgs args)
        {
            var networkEventMsg = new NetworkEventToSend();
            switch (eventType)
            {
                case NetworkEventType.JoinGame:
                    networkEventMsg.joinGameEventArgs = args as JoinGameEvent;
                    break;
                case NetworkEventType.ClientSendsInputMsg:
                    networkEventMsg.clientInputsEventArgs = args as ClientInputsEvent;
                    break;
                default:
                    _logUtility.Logging($"2. GameUdpClient 发送网络事件时枚举错误：{eventType}");
                    return;
            }
            networkEventMsg.eventType = eventType;
            var buffer = _jsonUtility?.GetBytesFromObjectWithUTF8(networkEventMsg);
            _udpClient?.Send(buffer, buffer.Length);
        }

        /// <summary>
        /// 在加入游戏时发送玩家自己的 Id 和名字
        /// </summary>
        void SendIdAndNameOnJoiningGame()
        {
            _logUtility.Logging($"1. GameUdpClient 发送 id 和名字：{_playerId} / {_playerName}");
            if (_networkEventField.joinGameEventArgs == null)
                _networkEventField.joinGameEventArgs = new JoinGameEvent();
            var args = _networkEventField.joinGameEventArgs;
            args.playerId = _playerId;
            args.playerName = _playerName;
            SendMsg(NetworkEventType.JoinGame, args);
            _logUtility.Logging($"1. GameUdpClient 发送 id 和名字完毕！");
        }

        /// <summary>
        /// 用于检测断连并重连
        /// </summary>
        /// <returns></returns>
        async Task CheckAndRetry()
        {
            var commonData = IOCContainer.GetInstance<IGameManager>().CommonData;
            float miliseconds = commonData.clientCheckDisconnectionTime * 1000;
            int waitTime = (int)(miliseconds * commonData.clientWaitingNotDisconnectionTimeMultiplier);
            int remainTime = (int)miliseconds - waitTime;
            var buffer = _jsonUtility.GetBytesFromObjectWithUTF8(new NetworkEventToSend()
            {
                eventType = NetworkEventType.NetworkDoesNotDisconnected,
            });
            // 发送事件给主线程，表示断连了
            var eventCenter = IOCContainer.GetInstance<IEventCenter>();
            var eventArgs = new GameHasDisconnectedEvent()
            {
                isTryingReconnection = true,
            };
            bool isFault = false;
            while (_cts.IsCancellationRequested == false)
            {
                // 等一段时间
                await Task.Delay(waitTime);
                int count = 0;
                for (; count < commonData.clientReconnectionCount; count++)
                {
                    // 发送心跳数据报
                    try
                    {
                        await _udpClient.SendAsync(buffer, buffer.Length);
                        // 剩下的时间内未收到回应则再发一次；
                        await Task.Delay(remainTime);
                        if (_isOnline)
                        {
                            _isOnline = false;
                            break;
                        }
                    }
                    catch (SocketException se)
                    {
                        isFault = true;
                        _logUtility.Logging($"2. GameUdpClient（检测）重连发生错误，可能是服务器关闭了……" +
                            $"{Environment.NewLine}{se}");
                        break;
                    }
                }
                // 若重发次数完了或者发送心跳包出现 Socket 异常，
                if (count >= commonData.clientReconnectionCount || isFault)
                {
                    eventCenter.SendEventOnNextFrame(eventArgs);
                    // 进行重连
                    int trycount = 0;
                    while (!_cts.IsCancellationRequested && trycount < commonData.clientReconnectionCount)
                    {
                        try
                        {
                            await _udpClient.SendAsync(buffer, buffer.Length);
                            // 剩下的时间内未收到回应则再发一次；
                            await Task.Delay(remainTime);
                            if (_isOnline)
                            {
                                // 发送事件给主线程，表示连接上了
                                eventCenter.SendEventOnNextFrame(new GameReconnectedSuccessfullyEvent());
                                break;
                            }
                            trycount++;
                            // 等待一会儿重试
                            await Task.Delay(waitTime);
                        }
                        catch (SocketException se)
                        {
                            trycount = commonData.clientReconnectionCount;
                            break;
                        }
                    }
                    if (trycount >= commonData.clientReconnectionCount)
                    {
                        // 给主线程通知未重连成功
                        eventArgs.isTryingReconnection = false;
                        eventCenter.SendEventOnNextFrame(eventArgs);
                        throw new Exception($"2. GameUdpClient：{commonData.clientReconnectionCount} 次尝试重连均未成功！");
                    }
                }
            }
        }

        public void SendInputs(Model.PlayerInputs inputs)
        {
            //_logUtility.Logging($"1. GameUdpClient 开始发送当前的输入……");
            if (_networkEventField.clientInputsEventArgs == null)
                _networkEventField.clientInputsEventArgs = new ClientInputsEvent();
            var args = _networkEventField.clientInputsEventArgs;
            args.clientFrame = _handledFrames;
            args.id = _playerId;
            args.name = _playerName;
            args.inputs = inputs.CheckInvaildInput() ? null : inputs;
            SendMsg(NetworkEventType.ClientSendsInputMsg, args);
            //_logUtility.Logging($"1. GameUdpClient 发送当前的输入完毕");
        }
        #endregion

        #region 抽象方法的实现
        protected override void OnPlayerTryJoiningGame(IPEndPoint ep, TryJoinningGameEvent e)
        {
            throw new NotImplementedException();
        }

        protected override void OnPlayerJoinedGame(IPEndPoint ep, JoinGameEvent e)
        {
            throw new NotImplementedException();
            //_logUtility.Logging($"1. GameUdpClient 接收到另一个玩家 id 和名字：{e.playerId} / {e.playerName}");
        }

        protected override void OnClientSendInputs(IPEndPoint ep, ClientInputsEvent e)
        {
            throw new NotImplementedException();
        }

        protected override void OnServerDispatchedInputs(ServerDispathesInputsEvent e)
        {
            // 如果收到的消息的帧数小于客户端已处理的，那么就舍弃该消息
            // 这么做是因为 UDP 有时序和丢包的问题
            if (e.serverFrame < _handledFrames)
                return;
            // TODO：应解决接收时间不同的问题，如两个客户端收到的消息为 67ms 和 68ms
            _inputsCache.Enqueue(e.allClientInputs);
            var eventCenter = IOCContainer.GetInstance<IEventCenter>();
            eventCenter.SendEventOnNextFrame(new ClientHasReceivedInputsEvent()
            {
                isToReissue = false,
                inputsCache = _inputsCache,
            });
        }

        protected override void OnServerReissuedInputs(ServerReissuesFramesEvent e)
        {
            foreach (var frame in e.reissuedInputs)
                _inputsCache.Enqueue(frame);
            var eventCenter = IOCContainer.GetInstance<IEventCenter>();
            eventCenter.SendEventOnNextFrame(new ClientHasReceivedInputsEvent()
            {
                isToReissue = true, 
                inputsCache = _inputsCache,
            });
        }

        protected override void OnNetworkNotDisconnect(IPEndPoint ep)
        {
            // 实际逻辑上考虑，可以不用同步基元
            _isOnline = true;
        }

        protected override void CheckDisconnection()
        {
            Task.Run(CheckAndRetry).ContinueWith(task =>
            {
                _logUtility.Logging($"1. GameUdpClient（检测）重连发生错误：{Environment.NewLine}" +
                    $"{task.Exception.InnerException}");
            }, TaskContinuationOptions.OnlyOnFaulted);
        }

        public override void OnClose()
        {
            _playerId = -1;
            _inputsCache.Clear();
            _playerName = null;
            _inputsCache = null;
            _cts = null;
            _udpClient = null;
        }
        #endregion
    }
}